package org.hawk.gwt.ppc.scanner.utils;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.hawk.gwt.ppc.utils.Name;
/**
 * Represents parameterized type extending usual type scope.
 * 
 * @author alex.bereznevatiy@gmail.com
 */
public class JavaParameterizedGenericTypeScope extends JavaGenericTypeScope {
	
	private String params;
	private List<JavaTypeScope> paramTypes;

	JavaParameterizedGenericTypeScope(Type superclass, JavaNameResolver owner) {
		this(toScope(superclass), owner, extractParameters(superclass));
	}

	JavaParameterizedGenericTypeScope(JavaTypeScope type, JavaNameResolver owner, String params) {
		super(type, owner);
		this.params = params;
		this.paramTypes = parseParams(params);
	}
	/**
	 * Recursively parses parameters represented by passed string.
	 * @param params
	 * @return list of parameterized types that represents parameters of current type.
	 */
	private List<JavaTypeScope> parseParams(String params) {
		if (params.length() < 3) {
			throw new InternalError("Bad type parameters: " + params);
		}
		List<JavaTypeScope> paramTypes = new ArrayList<JavaTypeScope>(1);
		JavaTokenizer tokenizer = new JavaTokenizer(params.substring(1, params.length() - 1));
		JavaTypeScope param;
		while ((param = parseParam(tokenizer)) != null) {
			paramTypes.add(param);
		}
		return Collections.unmodifiableList(paramTypes);
	}

	private JavaTypeScope parseParam(JavaTokenizer tokenizer) {
		if (!tokenizer.hasNext()) {
			return null;
		}
		String token = tokenizer.next();
		// on this stage we can filter out all the wildcard params:
		if (token.equals("?")) {
			return parseWildcardParam(tokenizer);
		}
		return parseUnknownType(tokenizer);
	}

	private JavaTypeScope parseUnknownType(JavaTokenizer tokenizer) throws InternalError {
		String token = tokenizer.current();
		/*
		 * Get type name:
		 */
		StringBuilder typeName = new StringBuilder();
		while (!token.equals(",") && 
				!token.equals("<") && 
				!token.equals(">")) {
			typeName.append(token);
			if (!tokenizer.hasNext()) {
				token = "";
				break;
			}
			token = tokenizer.next();
		}
		
		String rawType = typeName.toString();
		/*
		 * In case parameter is raw type or array of raw types, so we are done:
		 */
		if (token.isEmpty() || token.equals(",") || token.equals(">")) {
			return parseTypeOrTypeVariable(rawType);
		} else if (!token.equals("<")) {
			throw new InternalError("Bad parameters: " + this.params);
		}
		JavaTypeScope type = resolveTypeDescriptor(rawType).getCompilationUnit();
		/*
		 * In case parameter is parameterized type - we need to get type parameters
		 */
		type = new JavaParameterizedGenericTypeScope(type, getOwner(), getNestedParameters(tokenizer, token));
		if (!tokenizer.hasNext()) {
			return type;
		}
		token = tokenizer.next();
		if (!token.equals("[")) {
			return type;
		}
		return parseGenericArrayParam(tokenizer, type);
	}

	private JavaTypeScope parseTypeOrTypeVariable(String rawType) {
		String pureType = Name.getPureClassName(rawType);
		JavaTypeVariable var = getOwner().resolveTypeParameter(pureType);
		if (var == null) {
			return resolveTypeDescriptor(rawType).getCompilationUnit();
		}
		int deep = (rawType.length() - pureType.length()) / 2;
		JavaTypeScope result = var;
		while (deep --> 0) {
			result = new JavaArrayGenericTypeScope(result, getOwner());
		}
		return result;
	}

	private JavaTypeScope parseGenericArrayParam(JavaTokenizer tokenizer, JavaTypeScope type) {
		if (!tokenizer.next().equals("]")) {
			throw new InternalError("Bad parameters: " + this.params); 
		}
		JavaTypeScope result = new JavaArrayGenericTypeScope(type, getOwner());
		if (tokenizer.hasNext() && tokenizer.next().equals("[")) {
			return parseGenericArrayParam(tokenizer, result);
		}
		return result;
	}

	private String getNestedParameters(JavaTokenizer tokenizer,
			String token) throws InternalError {
		StringBuilder params = new StringBuilder();
		params.append(token);
		int deep = 1;
		while (deep > 0) {
			if (!tokenizer.hasNext()) {
				throw new InternalError("Bad parameters " + this.params);
			}
			token = tokenizer.next();
			if (token.equals("<")) {
				deep ++;
			} else if (token.equals(">")) {
				deep --;
			} else if (token.equals("extends") || token.equals("super")) {
				params.append(' ');
				params.append(token);
				params.append(' ');
				continue;
			}
			params.append(token);
		}
		return params.toString();
	}

	private JavaTypeScope parseWildcardParam(JavaTokenizer tokenizer) {
		if (!tokenizer.hasNext()) {
			return new JavaWildcardGenericTypeScope(getOwner(), null, ObjectTypeScope.INSTANCE);
		}
		String bound = tokenizer.next();
		if (bound.equals("super")) {
			tokenizer.next();
			return new JavaWildcardGenericTypeScope(getOwner(), parseUnknownType(tokenizer), ObjectTypeScope.INSTANCE);
		} else if (bound.equals("extends")) {
			tokenizer.next();
			return new JavaWildcardGenericTypeScope(getOwner(), null,parseUnknownType(tokenizer));
		}
		return new JavaWildcardGenericTypeScope(getOwner(), null, ObjectTypeScope.INSTANCE);
	}

	private static JavaTypeScope toScope(Type superclass) {
		if (superclass instanceof ParameterizedType) {
			Class<?> clazz = (Class<?>)((ParameterizedType)superclass).getRawType();
			return JavaClassDescriptorUtils.getClassDescriptor(clazz).getCompilationUnit();
		}
		throw new InternalError("Bad type: " + superclass);
	}

	private static String extractParameters(Type superclass) {
		if (superclass instanceof ParameterizedType) {
			Type[] args = ((ParameterizedType)superclass).getActualTypeArguments();
			StringBuilder result = new StringBuilder();
			for (Type arg : args) {
				if (result.length() > 0) {
					result.append(',');
				}
				result.append(arg);
			}
			return result.toString();
		}
		throw new InternalError("Bad type: " + superclass);
	}
	/**
	 * @return actual type parameters for this generic type.
	 */
	public List<JavaTypeScope> getTypeParameters() {
		return paramTypes;
	}

	@Override
	public String toString() {
		return super.toString() + params;
	}
}
